import abc
import collections
from dataclasses import dataclass
from datetime import datetime
from enum import IntEnum, Enum
from typing import List, Optional, Type, TypeVar


T = TypeVar("T")


class Interval(IntEnum):
    """
    Interval of bar data.
    """
    TICK = 0
    M1 = 1
    M5 = 6  ## 分钟
    M15 = 15  ## 15分钟
    M30 = 30
    H1 = 60
    HALF = 70  ## 半天

    R = 98  ##  实时

    DAILY = 100  ## 0，频率1天以下的值必须小于100
    WEEKLY = 110
    X = 102  ### 历史


    QUANT_ALL = 200  ## 量化整体统计数据
    QUANT_D25 = 201  ## 量化20天周期数据
    QUANT_D50= 202  ## 量化50天周期数据
    QUANT_D100= 203  ## 量化100天周期数据
    QUANT_D200= 204  ## 量化200天周期数据
    QUANT_D400= 205  ## 量化400天周期数据

    NULL = 999


    def is_daily(self)->bool:
        """
        是否日频级别及其以上
        :return:
        """
        return self.value < Interval.DAILY.value


@dataclass
class BarBase:
    symbol: str
    datetime: datetime
    interval: Interval

# class BarExtra:
#
#     @abc.abstractmethod
#     def to_bytes(self) -> bytes:
#         pass



@dataclass
class BarData(BarBase):

    """
    行情bar数据。
    """
    open: float = 0  ## 复权后的价格
    high: float = 0  ## 复权后的价格
    low: float = 0    ## 复权后的价格
    close: float = 0   ## 复权后的价格
    pre_close:float = 0 ## 上个交易日的收盘价。 一般情况下是没有的，需要执行BarStorage.update_pre_close()才有
    volume: float = 0
    amount:float = 0  ## 成交金额
    price_adjust:float = 1.0   ## 复权值
    _extra_bytes:bytes = None  ## 额外数据blob, 如果需要存储，需要在注册存储。

    def is_null(self):
        return self.datetime is None

    def reset_real_price(self):
        if abs(self.price_adjust - 1) > 0.001:
            self.open = self.open / self.price_adjust
            self.close = self.close / self.price_adjust
            self.low = self.low / self.price_adjust
            self.high = self.high / self.price_adjust
            self.pre_close = self.pre_close / self.price_adjust
            self.price_adjust = 1
        return

    def reset_price(self,pre_close:float):
        """
        重置所有价格。
        :param pre_close:
        :return:
        """
        pnl_close =  (self.close - self.pre_close) / self.pre_close
        pnl_high = (self.high - self.pre_close) / self.pre_close
        pnl_low = (self.low - self.pre_close) / self.pre_close
        pnl_open = (self.open - self.pre_close) / self.pre_close
        self.open = pre_close * (1 + pnl_open)
        self.low = pre_close * (1 + pnl_low)
        self.high = pre_close * (1 + pnl_high)
        self.close = pre_close * (1 + pnl_close)
        self.pre_close = pre_close

    @property
    def pnl(self):
        assert self.pre_close > 0
        return self.pnl_at(self.pre_close)

    def pnl_at(self, start_price: float):
        return 100 * (self.close - start_price) / start_price

    @property
    def tr(self):
        """
        振幅
        """
        _min = min(self.pre_close,self.low)
        _max = max(self.pre_close,self.high)
        _delta = _max - _min
        return 100 * _delta / self.pre_close

    @staticmethod
    def copy(bar, bar_class_type:Type[T]=None)->T:
        if bar is None:
            return None
        if bar_class_type is None:
            bar_class_type = bar.__class__
        bar_v2 = bar_class_type(
            symbol=bar.symbol,
            datetime=bar.datetime,
            interval=bar.interval,
            volume=bar.volume,
            amount =bar.amount,
            open=bar.open,
            high=bar.high,
            low=bar.low,
            close=bar.close,
            price_adjust=bar.price_adjust,
            pre_close = bar.pre_close
        )
        return bar_v2

    @staticmethod
    def merge_bars(bars: List, interval: Interval = None, bar_class_type: T = None, check=True) -> T:
        size = len(bars)
        if size == 0:
            return None
        bar = BarData.copy(bars[0], bar_class_type=bar_class_type)
        for i in range(1, size):
            if check:
                if not bar.datetime.date() == bars[i].datetime.date():
                    raise RuntimeError(f"merge_bars: 不在同一天, i ={i}, 应该:{bar.datetime},but:{bars[i].datetime}")
            assert bars[i - 1].datetime.timestamp() < bars[i].datetime.timestamp()
            bar.high = max(bars[i].high, bar.high)
            bar.low = min(bars[i].low, bar.low)
            bar.close = bars[i].close
            bar.volume += bars[i].volume
            bar.amount += bars[i].amount
        if not interval is None:
            bar.interval = interval
        return bar

    @staticmethod
    def null(symbol, bar_class_type: Type[T] = None,interval:Interval = Interval.DAILY) -> T:
        """
        构建一个null内容的bar
        :param symbol:
        :param bar_class_type:
        :return:
        """
        if bar_class_type is None:
            bar_class_type = BarData
        bar_v2 = bar_class_type(
            symbol=symbol,
            datetime= None,
            interval=interval,
            volume=None,
            amount=None,
            open=None,
            high=None,
            low=None,
            close=None,
            price_adjust=None,
            pre_close=None,
        )
        return bar_v2

    def get_extra(self,type:Type[T])->T:
        """
        获取额外的数据
        """
        raise NotImplementedError(f"不支持获取额外数据类型:{type}")


@dataclass
class BarList(BarData):
    """
    包含子行情的BarData。
    """

    _bars:List[BarData] = None  ##  当天行情的高频数据，即：pre_close、price_adjust与当天行情是一样的。

    _lazy_load_fun = None  ## 延迟加载:

    @staticmethod
    def of(sub_bars:List[BarData]):
        bar_list = BarList(symbol=sub_bars[0].symbol,
                           interval=sub_bars[0].interval,
                           datetime=sub_bars[0].datetime,
                           _bars=sub_bars
                           )
        bar_list.merge_value()
        return bar_list

    def bars(self)->List[BarData]:
        if self._bars is None and self._lazy_load_fun:
            self._bars = self._lazy_load_fun()
            self._lazy_load_fun = None
        return self._bars


    def merge_value(self):
        bars = self.bars()
        size = len(bars)
        if size == 0:
            return None
        self.open = bars[0].open
        self.close = bars[0].close
        self.high = bars[0].high
        self.low = bars[0].low
        self.pre_close = bars[0].pre_close
        self.price_adjust = bars[0].price_adjust
        self.volume = bars[0].volume
        self.amount = bars[0].amount
        bar = self
        for i in range(1, size):
            assert bars[i-1].datetime.timestamp() < bars[i].datetime.timestamp()
            bar.high = max(bars[i].high,bar.high)
            bar.low = min(bars[i].low,bar.low)
            bar.close = bars[i].close
            bar.volume += bars[i].volume
            bar.amount += bars[i].amount
        return bar

@dataclass
class BarCustom(BarBase):
    """
    自定义其它Bar数据，对应数据存储对象:BarCustomDB
    """
    _int1: int = -1
    _int2: int = -1
    _int3: int = -1

    _float1: int = -1
    _float2: int = -1
    _float3: int = -1

    _str1: str = None
    _str2: str = None
    _str3: str = None


    def get_blob(self)->Optional[bytes]:
        """
        存储时调用
        :return:
        """
        return None

    def set_blob(self,blob:Optional[bytes],form_storage = False):
        pass


@dataclass
class DailyBar(BarBase):
    """
    DailyBar的datime存储时是以date的形式存储。
    """
    _int1: int = -1
    _int2: int = -1
    _int3: int = -1

    _float1: float = -1
    _float2: float = -1
    _float3: float = -1

    _str1: str = None
    _str2: str = None
    _str3: str = None

    def get_blob(self) -> Optional[bytes]:
        """
        存储时调用
        :return:
        """
        return None

    def set_blob(self, blob: Optional[bytes], form_storage=False):
        pass

@dataclass()
class BarMeta():
    """
    个股元数据。如：
    名称、流通股、市盈率等等。
    """
    pass



@dataclass
class CategoryMeta(BarMeta):
    """
    分类行业结构数据
    """
    code:str
    name:str
    type:int = 0   ##0未定义，  1,表示大盘指数（上证指数，深证），2 表示行业大分类  3、表示行业小分类。
    #index_code:str = None  ## 所属index值的。

@dataclass
class SymbolMeta(BarMeta):
    """
    个股结构数据。
    """
    code: str  ## 个股代码
    name: str  ## 个股名称
    # pe: float = None  ##市盈率
    # eps: float = None  ##每股收益
    # gpr: float = None ##毛利率（%）
    # total_vol: int = None  ## 总股本
    # liquid_vol: int = None  ## 流动股本
    # ipo_time: datetime = None  ## 上市时间

@dataclass
class CategorySymbolMeta(BarMeta):

    code:str   ## 成员个股代码
    name:str   ## 成员个股名称
    category_code:str   ### 所属分类行业代码
    weight:float = 1   ## 权重

    def to_symbol_meta(self)->SymbolMeta:
        return SymbolMeta(name=self.name,code=self.code)

@dataclass
class CategoryMemberMeta(BarMeta):

    code:str   ## 成员个股代码
    name:str   ## 成员个股名称
    category_code:str = None   ### 所属分类行业代码
    weight:float = 1   ## 权重




@dataclass
class RankMeta(BarMeta):
    """
    排名行情元数据。
    """
    name: str   ## 名称
    label:str   ## 标签
    algo:str  ## 排名策略算法
    category_type: int = None  ## 如果是行业分类行情数据，返回对应CategoryMeta.type

@dataclass
class RankData:

    symbol:str
    datetime:datetime
    category_code:str = None      ## 若不为null，表示所属行业代码
    name:str = ""
    value:float = 0  ## 排名值。
    _extra:bytes = None  ## 额外数据
    ## 不需要自己计算，需要通过 RankData.make_rank()统一生成。
    _order:int = None  ## 排名次序，1表示最高, 不需要赋值，自动生成。1表示第1个。
    _value_avg:float = 0  ## 本次排名值的平均值
    _count:int = 0   ## 纳入排名的数量

    def copy(self):
        cls = self.__class__
        obj = cls(symbol=self.symbol,
                  datetime=self.datetime,
                  category_code=self.category_code,
                  name=self.name,
                  value=self.value,
                  _extra=self.get_extra_bytes(),
                  _order=self._order,
                  _value_avg=self._value_avg,
                  _count=self._count,
                  )
        return obj

    @property
    def order(self):
        return self._order

    @property
    def count(self):
        return self._count

    @property
    def value_avg(self):
        return self._value_avg

    def get_extra_bytes(self)->Optional[bytes]:
        """
        存储时调用。
        :return:
        """
        return self._extra

    @staticmethod
    def make_rank(bars:List['RankData']):
        """
        生成同一时间的排名信息：order,count,value_avg
        :param bars:
        :return:
        """
        if not bars:
            return
        #sorted_order = []
        count = len(bars)
        value_sum = 0
        for i in range(0, count):
            #sorted_order.append(i)
            value_sum += bars[i].value
        bars.sort(key=lambda _x:_x.value,reverse=True)
        ##sorted_order.sort(key=lambda k: bars[k].value, reverse=True)
        value_avg = value_sum / count
        for index, bar in enumerate(bars):
            bar._order = index + 1
            bar._value_avg = value_avg
            if bar._count is None:
                bar._count = count

@dataclass
class RankBar(RankData):

    interval:Interval = Interval.DAILY   ## detail里面的时间频率

    _details:List[RankData] = None
    _lazy_load_fun = None  ## 延迟加载:

    def details(self)->List[RankData]:
        """
        当天的详情数据。
        :return:
        """
        if self._details is None and self._lazy_load_fun:
            self._details = self._lazy_load_fun()
            self._lazy_load_fun = None
        return self._details







if __name__ == '__main__':

    print(f"{Interval.DAILY.value}")

    print(f"{Interval(0)}")